/*
This is the class which manages haggle connection, the class contains a
singleton reference to iself which all the Activities share.
(getInstance()).

From mainwindow in the application this getInstance is called to
initate haggle. if the instance is null it creates a HaggleConnector and
calls initHaggle(), which is in logic the same as Photoview with some
minor code change.

You also need to implement callback functions onInterestListUpdate(),
onNeighbourUpdate(), onNewDataObject(). Currently we only print out some
debug-info when there has been a callback.

shutdownHaggle should be called in mainwindow onDestroy(). So when an
activity calls finish() the mainwindow will kill Haggle in a proper
fashion, if this isn't working correctly in your app it might break
haggle.db, giving error code -96 when trying to start Haggle the next
time. You solve this by using adb shell and writing "rm -r
/data/haggle", I assume you have Android SDK and it's tool adb running.

For testing purpose I recommend that you create an activity with several
buttons, such as "register interest", "unregister interest", "shutdown"
and finally "create obj". Here you need to call
getInstance().getHaggleHandle().registerInterest(a) where a is an
Attribute,  to register interest, check the source code of java API for
details and also libhaggle/include/libhaggle/attribute.h for details on
what parameters attribute has. A quick explanation is that the
constructur of Attribute takes name(String), value(String)
weight(long)), we use weight 1 on everything, but look for details in
attribute.h if you want to know more about it.

Unregister interest is similar but unregisters interest, shutdown should
just call activitiy's finish() method. One important thing here is to
look in process list on the phone (using adb shell, "ps") to see if
haggle daemon acctually is killed or not. When the app shutsdown the
process is till alive due to wierd garbage collection is android, which
you will have to destroy, otherwise the program is re-started and it's
onResume method is called, you could initiate haggle again if it's dead
but we will probably use finihaggle for onStop/onPause and
shutdownHaggle on onDestroy. anyway this could be a problem. One
indication of if haggle is "starting" in the beginning is that the app
takes 1-2 sec longer to start.

After you have implemented these methods you can try to start the
application
In LogCat you will see "haggle event loop started" if the haggle daemon
is correctly started otherwise there should be some error code, which
you can find here http://pastebin.com/9N4TasyG (we exported them from
filedump).

you can now test the Application if it's haggle connection works by
trying to communicate with another mobile running PhotoView. Though you
have to start bluetooth before starting the application, it won't work
otherwise. PhotoView should find the device and vibrate and print
"localhost [BT]". LogCat should also print out "onNeighbourupdate".

After this is done, try to register an interest, and you should get a
status code of >0, if there is a 0 in status code something went wrong.
(Photoview code on this might confuse you but check the source code of
haggle at haggle_attributelist_add_attribute on what it acctually does.

When the registration is done try to create an object and push it down
to haggle, you need to have a filepath, the Java API doesn't permit null
or empty paths, either you add on a specific constructor in the java API
for publish or you have some file where you refer to when creating the
object.

If the publish went allright you should get a callback on onNewData with
the same object, everything that gets pushed down gets also pushed up.
Now if you run the app on two phones, register the same interest it
should pass along the dObj!

I hope this wasn't to poorly written, good luck.

/Sam
 * */

package com.googlecode.eightqueenshaggle;


import java.util.ArrayList;

import org.haggle.Attribute;
import org.haggle.DataObject;
import org.haggle.EventHandler;
import org.haggle.Node;
import org.haggle.Handle;
import org.haggle.DataObject.DataObjectException;
import android.util.Log;

public class HaggleConnector implements EventHandler {
	private static final String HAGGLE_TAG = "EQHaggle"; 
	public static final String STORAGE_PATH = "/sdcard/EQHaggle";
	
	private Handle hh;
	
	private static final int STATUS_OK = 0;
	private static final int STATUS_REG_FAILED = -2;
	private static final int STATUS_SPAWN_DAEMON_FAILED = -3;
	
	private static  final int NUM_RETRIES = 2;
	
	public static EightQueens eqHandler;
	
	//Singleton instance
	private static HaggleConnector uniqueInstance;
	
	// We buffer all the solutions and neighbors that we have received so far in here
	public ArrayList<SolutionObject> solobjs=new ArrayList<SolutionObject>();
	public Node[] neighs = null;
	
	/* Such that several Activities in Front end can share the same HaggleConnector object*/
	public static synchronized HaggleConnector getInstance() {
		if (uniqueInstance == null) {
			uniqueInstance = new HaggleConnector();
					
			//initialize Haggle
			uniqueInstance.initHaggle();
		}
		
		//solObjArray.add(new SolutionObject(new Puzzle(),new Solution(null),"Mr. Serious"));
		
		return uniqueInstance;
	}
	/* this could be private later on */
	public Handle getHaggleHandle() {
		if (hh == null) {
			Log.e(getClass().getSimpleName(), "Haggle Handle is null!");
		}
		
		return hh;
	}
	
	private int initHaggle() {
		if (hh != null)
			return STATUS_OK; 

		Log.d(getClass().getSimpleName(), "Trying to Spawn haggle daemon");
		
		if (!Handle.spawnDaemon()) {
			Log.d(getClass().getSimpleName(), "Spawning failed");
			return STATUS_SPAWN_DAEMON_FAILED;
		}
		
		long pid = Handle.getDaemonPid(); 
		Log.d(getClass().getSimpleName(), "Haggle daemon pid is: " + pid);

		
		for (int tries = 0; tries < NUM_RETRIES; tries++) {
			try {
				hh = new Handle(HAGGLE_TAG);
				
				hh.registerEventInterest(EVENT_NEIGHBOR_UPDATE, this);
				hh.registerEventInterest(EVENT_NEW_DATAOBJECT, this);
				hh.registerEventInterest(EVENT_INTEREST_LIST_UPDATE, this);
				hh.registerEventInterest(EVENT_HAGGLE_SHUTDOWN, this);
				
				hh.eventLoopRunAsync();
				hh.getApplicationInterestsAsync();
				
				Log.d(getClass().getSimpleName(), "Haggle event loop started");
				
				return STATUS_OK;
				 
			} catch (Handle.RegistrationFailedException e) {
				Log.e(getClass().getSimpleName(), "Registration failed: " + e.getMessage());
				
				Handle.unregister(HAGGLE_TAG);
			}
		}

		Log.e(getClass().getSimpleName(), "Registration failed, after retries");
		return STATUS_REG_FAILED;
	}
	
	/* drop current haggle connection? */
	public synchronized void finiHaggle() {
		if (hh != null) {
			hh.eventLoopStop();
			hh.dispose();
			hh = null;
		}
	}
	
	public int shutdownHaggle() {
		if (hh != null) {
			return hh.shutdown();
		}
		return 1;
	}
	

	public void onInterestListUpdate(Attribute[] arr) {
		Log.d("********** onInterestListUpdate", "got new interests, size: " +arr.length);
		for (Attribute a : arr) {
			Log.d(null, "********** Attr: " + a.getName() + ", value:" + a.getValue());
		}
	}

	
	public void onNeighborUpdate(Node[] neighbors) {
		Log.d("**********", "***********");
		for (Node n : neighbors) {
			Log.d(null, "********** NeighborUpdate: " + n.getName());
		}
	
		//buffer the neighbors
		neighs = neighbors;
		
	
		/*for (Node n : neighs) {
			Log.d(null, "********** NeighborUpdate: " + n.getName());
		}*/	
		
		//inform the GUI
		if(eqHandler.solutionExplorerViewHandler != null)
			eqHandler.solutionExplorerViewHandler.
		runOnUiThread(
				eqHandler.solutionExplorerViewHandler.
				new DataUpdater(org.haggle.EventHandler.EVENT_NEIGHBOR_UPDATE)
				);
	
	}

	
	public void onNewDataObject(DataObject dObj) {
		
		Log.d("********** :onNewDataObject", "Got new data!");
		
		if (dObj == null) {
			Log.e("********** :onNewDataObject", "dObj null");
		}
		
		/*Attribute[] all = dObj.getAttributes();
		
		if (all != null) {
			for (Attribute a : all) {
				Log.d("********** :onNewDataObject", a.getName() + ", " + a.getValue());
			}
		} else {
			Log.e("********** onNewDataObject", "ALL IS NULL");
		}*/
		
		
		//TODO: Check which kind of puzzle the received dobj belongs to and if it fits our interests
		
		//create and store the received data into a SolutionObject
		
		String puzzleName = dObj.getAttribute("puzzleName", 0).getValue();
		String playerName = dObj.getAttribute("playerName", 0).getValue();
		int dim1 = Integer.parseInt(dObj.getAttribute("dim1", 0).getValue());
		int dim2 = Integer.parseInt(dObj.getAttribute("dim2", 0).getValue());
		int amountQueens = Integer.parseInt(dObj.getAttribute("amountQueens", 0).getValue());
		int amountSolutions = Integer.parseInt(dObj.getAttribute("amountSolutions", 0).getValue());
	
		
		
		Puzzle recvPuzzle = new Puzzle(dim1, dim2, amountQueens, amountSolutions, puzzleName);
		
		Solution recvSolution = new Solution(recvPuzzle);
		recvSolution.readCoordString(dObj.getAttribute("coord", 0).getValue());
		
		SolutionObject recvSolObj = new SolutionObject(recvPuzzle, recvSolution, playerName);
		
		solobjs.add(recvSolObj);
		

		//inform the GUI
		if(eqHandler.solutionExplorerViewHandler != null)
			eqHandler.solutionExplorerViewHandler.
		runOnUiThread(
				eqHandler.solutionExplorerViewHandler.
				new DataUpdater(org.haggle.EventHandler.EVENT_NEW_DATAOBJECT)
				);
		
	}

	public void onShutdown(int reason) {
		Log.e("********** Haggle Shutdown, reason: " , reason+" ");
		
	}



	public ArrayList<SolutionObject> getAllObjects() {
		// TODO Auto-generated method stub
		return solobjs;
	}


	public SolutionObject getSolutionObject(int id) {
		// TODO Auto-generated method stub
		
		return solobjs.get(id);
	}

	
	public int pushSolutionObject(SolutionObject o) {
		try {
			DataObject dObj = new DataObject();
			
			Log.d("***Pushing Data Object****", "puzzleName " + o.myPuzzle.puzzleName);
			
			dObj.addAttribute("AppName", "EightQueens", 1);
			dObj.addAttribute("puzzleName", o.myPuzzle.puzzleName, 1);
			dObj.addAttribute("playerName", o.playerName, 1);
			dObj.addAttribute("dim1", Integer.toString(o.myPuzzle.dim1), 1);
			dObj.addAttribute("dim2", Integer.toString(o.myPuzzle.dim2), 1);
			dObj.addAttribute("amountQueens", Integer.toString(o.myPuzzle.amountQueens), 1);
			dObj.addAttribute("amountSolutions", Integer.toString(o.myPuzzle.amountSolutions), 1);
			dObj.addAttribute("coord", o.mySolution.getCoordString(), 1);
			
			Log.d("***String functions: Check coord string", o.mySolution.getCoordString());
			
			getHaggleHandle().publishDataObject(dObj);
			
			
			
		} catch (DataObjectException e) {
			Log.e(getClass().getSimpleName(), "Could not publish solution");
			Log.e(getClass().getSimpleName(), e.getMessage());
		}
		
		return 0;
	}
}